package com.lingyuting.orm;

import java.sql.Connection;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

public abstract class ActiveRecord<T> {

    private Connection conn = null;

    private Map<String, Object> attributes = new HashMap<String, Object>();

    /**
     * 初始化数据库
     */
    public ActiveRecord() {
        conn = getDbConnection();

        if ("" == getTable()) {
            throw new RuntimeException("must implemt implement getTable method");
        }
    }

    /**
     * override this method in your own model
     * 
     * @return
     */
    public Connection getDbConnection() {
        return DbManager.getInstance().getConnection();
    }

    public void setAttribute(String k, Object v) {
        attributes.put(k, v);
    }

    public void setAttributes(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    public Object getAttribute(String key) {
        return attributes.get(key);
    }

    /**
     * 创建一个实例
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    public ActiveRecord<T> createNewInstance() {
        try {
            return this.getClass().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 数据库中对应的表名
     * @return
     */
    public abstract String getTable();

    /**
     *  主键的名称
     * @return
     */
    public String getPrimaryKey() {
        return "id";
    }

    /**
     * 表的别名
     * @return
     */
    public String getAlias() {
        return "t";
    }

    @SuppressWarnings("unchecked")
    public T findByPk(String id) {
        String sql = "select * from `{0}` `{1}`  where `{1}`.`{2}` = ?";
        sql = MessageFormat.format(sql, this.getTable(), this.getAlias(),
                this.getPrimaryKey());
        ActiveRecord<T> model = createNewInstance();
        Map<String, Object> data = (Map<String, Object>) this.query(sql, true,
                id);
        if (null == data) {
            return null;
        }
        model.setAttributes(data);
        return (T) model;
    }

    @SuppressWarnings("unchecked")
    public T find(String sql, Object... params) {
        sql = "select * from `" + getTable() + "` where " + sql + " limit 1";
        Map<String, Object> data = (Map<String, Object>) this.query(sql, true,
                params);
        if (null == data) {
            return null;
        }
        ActiveRecord<T> model = createNewInstance();
        model.setAttributes(data);
        return (T) model;
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll(String sql, Object... params) {
        sql = "select * from `" + getTable() + "` where " + sql;

        List<Map<String, Object>> list = (List<Map<String, Object>>) this
                .query(sql, false, params);
        List<T> result = new ArrayList<T>();
        for (Map<String, Object> map : list) {
            ActiveRecord<T> model = createNewInstance();
            model.setAttributes(map);
            result.add((T) model);
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        String sql = "select * from `" + getTable() + "`";

        List<Map<String, Object>> list = (List<Map<String, Object>>) this
                .query(sql, false);
        List<T> result = new ArrayList<T>();
        for (Map<String, Object> map : list) {
            ActiveRecord<T> model = createNewInstance();
            model.setAttributes(map);
            result.add((T) model);
        }
        return result;
    }

    public int update() {
        Object id = attributes.get(getPrimaryKey());
        if (null == id) {
            throw new RuntimeException("primaryKey need be set");
        }
        Map<String, Object> attrs = attributes;
        attrs.remove(getPrimaryKey());
        return updateByPk(id.toString(), attrs);
    }

    public int updateByPk(String id, Map<String, Object> attrs) {
        Set<String> keys = attrs.keySet();
        Object[] params = new Object[keys.size() + 1];
        String sql = "update `" + getTable() + "` set ";
        int index = 0;
        for (String key : keys) {
            sql = sql + "`" + key + "`=?,";
            params[index] = attrs.get(key);
            index++;
        }
        sql = sql.substring(0, sql.length() - 1);
        sql = sql + " where " + getPrimaryKey() + " = ?";
        params[keys.size()] = id;
        return excute(sql, params);
    }
    
    private Object[] arrayCombine(Object[] a, Object[] b)
    {
        Object[] c = new Object[a.length + b.length];
        System.arraycopy(a, 0, c, 0, a.length);
        System.arraycopy(b, 0, c, a.length, b.length);
        return c;
    }

    public int updateAll(Map<String, Object> change, String sql,
            Object... params) {
        String baseSql = "UPDATE `" + getTable() + "` set ";
        Set<String> keys = change.keySet();
        Object[] updates = new Object[keys.size()];
        int index = 0;
        for (String key : keys) {
            baseSql = baseSql + "`" + key + "`=?,";
            updates[index] = change.get(key);
            index++;
        }
        baseSql = baseSql.substring(0, baseSql.length() - 1);
        baseSql = baseSql + " WHERE " + sql;
        return excute(baseSql, arrayCombine(updates, params));
    }

    public boolean save() {
        if (null != attributes.get(getPrimaryKey())) {
            return update() > 0;
        }
        Set<String> keys = attributes.keySet();
        Object[] params = new Object[keys.size()];
        String sql = "INSERT INTO `" + getTable() + "` (";
        String placeHolder = "";
        int index = 0;
        for (String key : keys) {
            sql = sql + "`" + key + "`,";
            placeHolder = placeHolder + "?,";
            params[index] = attributes.get(key);
            index++;
        }
        sql = sql.substring(0, sql.length() - 1);
        placeHolder = placeHolder.substring(0, placeHolder.length() - 1);
        sql = sql + ") VALUES (" + placeHolder + ")";
        int result = excuteNotClose(sql, params);
        if (result > 0) {
            String id = getInsertId();
            attributes.put(getPrimaryKey(), id);
        }
        return result > 0;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private String getInsertId() {
        QueryRunner queryRunner = new QueryRunner(true);
        String id;
        try {
            id = queryRunner.query(conn, "SELECT LAST_INSERT_ID()",
                    new ScalarHandler(1)).toString();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return id;
    }

    /**
     * @param sql
     * @param params
     * @return
     */
    public int excute(String sql, Object... params) {
        QueryRunner queryRunner = new QueryRunner(true);
        int result = 0;
        try {
            result = queryRunner.update(conn, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 查询但是并不关闭链接
     * 
     * @param sql
     * @param params
     * @return
     */
    private int excuteNotClose(String sql, Object... params) {
        QueryRunner queryRunner = new QueryRunner(true);
        int result = 0;
        try {
            result = queryRunner.update(conn, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return result;
    }

    private Object query(String sql, boolean isOne, Object... params) {
        QueryRunner qr = new QueryRunner();
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        try {
            list = qr.query(conn, sql, new MapListHandler(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }

        if (isOne) {
            return list.size() > 0 ? list.get(0) : null;
        }
        return list;
    }

}
